import * as THREE from 'three';

import { Vector2 } from 'three';

// 获取2D图形的中心点
const get2dCenter = () => {};
export function getBoundingBoxByObject3D(object /* : THREE.Object3D */) { // 根据 Object3D 获取包围盒对象 Box3
    return new THREE.Box3().setFromObject(object);
}

export function getCenterByObject3D(object/* : THREE.Object3D */) { // 获取 Object3D 对象的中心点
    const v = new THREE.Vector3();
    getBoundingBoxByObject3D(object).getCenter(v);
    return v;
}

export function forMaterial(materials, callback) {
    if (!callback || !materials) return false;
    if (Array.isArray(materials)) {
        materials.forEach((mat) => {
            callback(mat);
        });
    } else {
        callback(materials);
    }
}

// @ts-ignore
export function buildFlatSpace(arr, acc = []) { // 处理地图空间数据
    arr.map(({
        children, spaces, spaceTreeCode, parentTreeCode
    }) => {
        const isLeaf = !(children && children.length); // 是否是叶子节点
        if (spaces.length > 0) {
            spaces.forEach((data) => {
                const modelData = {
                    ...data,
                    type: data.drawMapTreeCode,
                    treeCode: parentTreeCode || spaceTreeCode,
                    spaceTreeCode
                };
                acc.push(modelData);
            });
        }
        if (!isLeaf) buildFlatSpace(children, acc);
    });
    return acc;
}

// 弧度转角度
export const rad2angle = (rad) =>{
    return rad / Math.PI * 180;
}
/**
 * 节流器
 * @param {Function} fun 功能函数
 * @param {Number} delay 节流间隔
 * @returns {Function} 经节流器包装后生成的新函数, 其参数同 fun
 */
export const throttle = (fun, delay) => {
    let last, deferTimer;
    return (...args) => {
        const argsCopy = args;
        const now = new Date();
        let intervalTime;
        if (last) {
            intervalTime = now - last;
        } else {
            intervalTime = 0;
        }
        if (intervalTime < delay) {
            clearTimeout(deferTimer);
            deferTimer = setTimeout(() => {
                last = now;
                fun.apply(this, argsCopy);
            }, delay);
        } else {
            last = now;
            fun.apply(this, argsCopy);
        }
    };
};

export const immediate = (fn, delay) => {
    let canRun = true;
    return function() {
        if (canRun) {
            fn.apply(this, arguments);
            canRun = false;
            setTimeout(() => {
                canRun = true;
            }, delay);
        }
    }
}

/**
 * @method debounce 防抖函数
 * @param {Function} fn 执行函数
 * @param {Number} wait 间隔时间
 * @param { Boolean } immediate 是否立即执行
 */
export const debounce = (fn, wait = 300, immediate = true) => {
    let timer;
    return (...args) => {
        const ctx = this;
        if (timer) clearTimeout(timer);
        if (immediate) {
            const callNow = !timer;
            timer = setTimeout(() => {
                timer = null;
            }, wait);
            if (callNow) fn.apply(ctx, args);
        } else {
            timer = setTimeout(() => fn.apply(ctx, args), wait);
        }
    };
};

// 角度转弧度
export const angle2rad = (angle) => {
    return angle / 180 * Math.PI;
}

export function getAllUrlParams() {
    // get query string from url (optional) or window
    let queryString = window.location.href.split('?')[1];

    // we'll store the parameters here
    const obj = {};

    // if query string exists
    if (queryString) {
        // stuff after # is not part of query string, so get rid of it
        queryString = queryString.split('#')[0];

        // split our query string into its component parts
        const arr = queryString.split('&');

        for (let i = 0; i < arr.length; i++) {
            // separate the keys and the values
            const a = arr[i].split('=');

            // set parameter name and value (use 'true' if empty)
            let paramName = a[0];
            const paramValue = typeof (a[1]) === 'undefined' ? true : a[1];

            // (optional) keep case consistent
            paramName = paramName.toLowerCase();

            // if the paramName ends with square brackets, e.g. colors[] or colors[2]
            if (paramName.match(/\[(\d+)?\]$/)) {
                // create key if it doesn't exist
                const key = paramName.replace(/\[(\d+)?\]/, '');
                if (!obj[key]) obj[key] = [];

                // if it's an indexed array e.g. colors[2]
                if (paramName.match(/\[\d+\]$/)) {
                    // get the index value and add the entry at the appropriate position
                    const index = /\[(\d+)\]/.exec(paramName)[1];
                    obj[key][index] = paramValue;
                } else {
                    // otherwise add the value to the end of the array
                    obj[key].push(paramValue);
                }
            } else {
                // we're dealing with a string
                if (!obj[paramName]) {
                    // if it doesn't exist, create property
                    obj[paramName] = paramValue;
                } else if (obj[paramName] && typeof obj[paramName] === 'string') {
                    // if property does exist and it's a string, convert it to an array
                    obj[paramName] = [obj[paramName]];
                    obj[paramName].push(paramValue);
                } else {
                    // otherwise add the property
                    obj[paramName].push(paramValue);
                }
            }
        }
    }

    return obj;
}

export function toggleObject3DVisible(object/* : THREE.Object3D */, visible/* : Boolean */) { // Object3D 对象的显示与隐藏切换
    object.visible = visible || !object.visible;
}

export function getBoundingSphereByObject3D(object /* : THREE.Object3D */) { // 根据 Object3D 获取包围球对象 Sphere
    return getBoundingBoxByObject3D(object).getBoundingSphere(new THREE.Sphere());
}

export function showBoundingBoxByObject3D(object /* : THREE.Object3D */, scene) { // 根据 Object3D 获取包围盒对象 Box3 并模拟展示
    const boundingBox = getBoundingBoxByObject3D(object);
    const helper = new THREE.Box3Helper(boundingBox, 0xff0000);
    scene.add(helper);
}

export function showBoundingSphereOutlineByObject3D(object /* : THREE.Object3D */, scene/* : THREE.Scene */) { // 展示实体包围球
    const boundingSphere = getBoundingSphereByObject3D(object);
    const geometryCenter = boundingSphere.center;
    const geometry = new THREE.SphereGeometry(boundingSphere.radius, 32, 16);

    geometry.translate(geometryCenter.x, geometryCenter.y, geometryCenter.z);
    const material = new THREE.MeshBasicMaterial({
        color: 0xff0000,
        transparent: true,
        // 设置材质透明度
        opacity: 0.4
    });

    const sphere = new THREE.Mesh(geometry, material);
    scene.add(sphere);
}

export function showBoundingBoxOutlineByObject3D(object /* : THREE.Object3D */, scene/* : THREE.Scene */) { // 展示实体包围盒
    const boundingBox = getBoundingBoxByObject3D(object);
    const geometrySize = boundingBox.getSize();
    const geometryCenter = boundingBox.getCenter();
    const geometry = new THREE.BoxGeometry(geometrySize.x, geometrySize.y, geometrySize.z);

    geometry.translate(geometryCenter.x, geometryCenter.y, geometryCenter.z);
    const material = new THREE.MeshBasicMaterial({
        color: 0xff0000,
        transparent: true,
        // 设置材质透明度
        opacity: 0.4
    });

    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);
}

export function moveCameraByObject3D(camera/*: THREE.Camera */, object /* : THREE.Object3D */, control/*: THREE.OrbitControls */, offset = 280 /* 偏移量 */) { // 根据传入的物体移动相机位置（获取最佳位置）
    /// doc: https://github.com/mrdoob/three.js/issues/6784
    const boundingBox = getBoundingBoxByObject3D(object); // 获取包围盒
    const boundingSphere = boundingBox.getBoundingSphere(new THREE.Sphere()); // 获取包围球

    control.target.copy(boundingSphere.center); // 改变控制器
    control.update();

    const distance = boundingSphere.radius / Math.tan(camera.fov / 2); // 摄像机距离中心点的坐标
    camera.position.copy(boundingSphere.center.clone().add(new THREE.Vector3(distance + offset, 0, 0)));
    // camera.position.copy(camera.getWorldDirection().normalize().negate().clone().multiplyScalar(boundingSphere.radius * 2).clone().add(boundingSphere.center));
}

export function createLineByVector3(pointStart, pointEnd, options = {}) { // 点连线，适用于创建模型之间的连线
    const material = new THREE.LineBasicMaterial({ ...options });
    const geometry = new THREE.BufferGeometry();
    const controlPoint = pointStart.clone().add(pointEnd).divideScalar(2).add(new THREE.Vector3().setY(60));
    const curve = new THREE.QuadraticBezierCurve3(pointStart, controlPoint, pointEnd);
    const pointers = curve.getPoints(99);
    geometry.setFromPoints(pointers);
    return { mesh:new THREE.Line(geometry, material), center:pointers[49] };
}

export function setMehsUpToY(mesh) {
    mesh.applyMatrix4(new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), -Math.PI / 2));
}

/**
 * 防碰撞算法
 * @param {Object} rectA 矩形a
 * @param {Object} rectB 矩形b
 * @returns Boolean
 */
export function d2BoxAntiCheck(rectA, rectB) {
    return (rectA.maxX - rectB.minX) * (rectB.maxX - rectA.minX) > 0
     && (rectA.maxY - rectB.minY) * (rectB.maxY - rectA.minY) > 0;
}


export const uuid = (length = 8, chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') => {
    let result = '';
    for (let i = length; i > 0; --i) {
        let char = chars[Math.floor(Math.random() * chars.length)];
        while(result.indexOf(char) >= 0){
            char = chars[Math.floor(Math.random() * chars.length)];
        }
        result += char
    };
    return result;
};

/**
 * 将3维坐标转换为 2维像素坐标
 * @param {Vector3} d3Pos 3d世界中的坐标
 * @param {Camera} camera threejs 相机对象
 * @param {Number} canvasHalfWidth 画布宽度的一半
 * @param {Number} canvasHalfHeight 画布高度的一半
 * @returns {Vector2}
 */
export const d3PosTod2Pos = (d3Pos, camera, canvasHalfWidth, canvasHalfHeight) => {
    if (d3Pos) {
        const pos = d3Pos.clone();
        pos.project(camera);
        return new Vector2(Math.round(pos.x * canvasHalfWidth + canvasHalfWidth),Math.round(-pos.y * canvasHalfHeight + canvasHalfHeight));
    }
    return null;
}

export default {
    get2dCenter
};
